/**
 * @fileoverview
 *
 * sensor to collect browser events
 *
 * @author Sebastian Brzuzek sebastian@brzuzek.pl
 */

goog.provide('uxperf.profiler.SensorBrowserEvent');
goog.provide('uxperf.profiler.BufferItemBrowserEvent');

goog.require('uxperf.profiler.SensorBase');
goog.require('uxperf.BufferItem');
goog.require('uxperf.BufferItemSensor');

/**
 * Browser event sensor implementation
 *
 * @param {number} index number of sensor in configuration structure
 * @param {uxperf.Queue} opt_queue optional queue to store events (global queue instance for default)
 * 
 * @constructor
 * @extends {uxperf.profiler.SensorBase}
 */
uxperf.profiler.SensorBrowserEvent = function(index, opt_queue) {
	goog.base(this, index, uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG, opt_queue);
  if (goog.DEBUG) LOG.info('SensorBrowserEvent - construction done (sensor index=' + index + ')');
};
goog.inherits(uxperf.profiler.SensorBrowserEvent, uxperf.profiler.SensorBase);

/**
 * calculate target from param
 * @param {*} targetParam value of parameter which defines target
 *            real type allowed here is {Element|string|Function}
 *            but to reduce complexity of invocation we skip
 *            verification on compile time
 * @return {Element}
 */
uxperf.profiler.SensorBrowserEvent.prototype.calcTarget = function (targetParam) {
  if (goog.DEBUG) LOG.info('SensorBrowserEvent - calcTarget - param value: ' + targetParam);

  /**
   * @type {Element|string|Function}
   */
  var trgPrm = /** @type {Element|string|Function} */ targetParam;
  
	/**
	 * @type {Element|string}
	 */
	var tg = null;
  if (goog.isFunction(trgPrm)) {
    tg = /** @type {Element|string} */ trgPrm.call(this);
  } else {
    tg = /** @type {Element|string} */ trgPrm;
  }

  return goog.dom.getElement(tg);
};

/**
 * Initialization of sensor
 *
 * NOTE: if you want to use targets other than window or document
 *       it is important to call init when DOM structure is ready
 *
 * @return {uxperf.profiler.SensorBrowserEvent} this sensor instance
 * @override
 */
uxperf.profiler.SensorBrowserEvent.prototype.init = function() {
  if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization');
  //verify if already initialized
	if (this.isReady()) return this;
  
	try {
		//register listeners related to browser events specified in configuration
    
		/**
		 * @type {string|Array|Object}
		 */
		var eventType = /** @type {string|Array|Object} */ this.getParam(uxperf.profiler.SensorBrowserEvent.CONFIG.EVENT_TYPE);
    if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization - eventType: ' + eventType);

		/**
		 * @type {Element}
		 */
    var commonEventTarget = this.calcTarget(this.getParam(uxperf.profiler.SensorBrowserEvent.CONFIG.EVENT_TARGET));
    if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization - commonEventTarget: ' + commonEventTarget);
		
		/**
		 * @type {boolean}
		 */
		var commonCapture = /** @type {boolean} */ this.getParam(uxperf.profiler.SensorBrowserEvent.CONFIG.CAPTURE);
    if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization - commonCapture: ' + commonCapture);
    
    if (goog.isString(eventType)) eventType = [eventType];
		if (goog.isArray(eventType)) {
      if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization with array');
      for (var i=0; i<eventType.length; i++) {
    		/**
         * @type {Element}
         */
        var target = commonEventTarget;
        
				/**
				 * @type {boolean}
			   */
        var capture = commonCapture;
        
        /**
         * @type {string}
         */
        var evType = goog.isString(eventType[i]) ? /** @type {string} */ eventType[i] : '?';
        if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization - evType: ' + evType);
        if (goog.isObject(eventType[i])) {
          if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization - evType is Object');
          evType = /** @type {string} */ eventType[i][uxperf.profiler.SensorBrowserEvent.CONFIG.EVENT_TYPE];
          if (evType) {
            if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization - target: ' + eventType[i][uxperf.profiler.SensorBrowserEvent.CONFIG.EVENT_TARGET]);
            if (eventType[i][uxperf.profiler.SensorBrowserEvent.CONFIG.EVENT_TARGET])
              target = this.calcTarget(eventType[i][uxperf.profiler.SensorBrowserEvent.CONFIG.EVENT_TARGET]);
            if (eventType[i][uxperf.profiler.SensorBrowserEvent.CONFIG.CAPTURE]) capture = eventType[i][uxperf.profiler.SensorBrowserEvent.CONFIG.CAPTURE];
          }
        }
        if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization - evType: ' + evType + ', capture: ' + capture + ', target: ' + target);
        if (evType && target) goog.events.listen(target, evType, this, capture, this);
      }
		} else if (goog.isObject(eventType)) {
      if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization with object');
			goog.object.forEach(eventType, function(value, type) {
					var target = commonEventTarget;
					var capture = commonCapture;
					if (goog.isObject(value)) {
						if (value[uxperf.profiler.SensorBrowserEvent.CONFIG.EVENT_TARGET])
              target = this.calcTarget(value[uxperf.profiler.SensorBrowserEvent.CONFIG.EVENT_TARGET]);
						if (value[uxperf.profiler.SensorBrowserEvent.CONFIG.CAPTURE]) capture = value[uxperf.profiler.SensorBrowserEvent.CONFIG.CAPTURE];
					} else {
						target = this.calcTarget(value) || target;
					}
          if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization - type: ' + type);
					goog.events.listen(target || commonEventTarget, type, this, capture, this);
				},
				this
			);
		}
	} catch (e) {
		this.errorNotify(e);
	}

	this.readyNotify();
  if (goog.DEBUG) LOG.info('SensorBrowserEvent - initialization done');
	return this;
};

/**
 * @inheritDoc
 */
uxperf.profiler.SensorBrowserEvent.prototype.convert = function (event) {
  if (goog.DEBUG) LOG.info('SensorBrowserEvent - convert');
	return uxperf.profiler.SensorBrowserEvent.superClass_.convert.call(
    this,
    event,
    new uxperf.BufferItemBrowserEvent(
      new goog.events.BrowserEvent(/** @type {Event} */ (event)),
      this
    )
  );
};

/**
 * constants for config parameters
 * 
 * @enum {string}
 */
uxperf.profiler.SensorBrowserEvent.CONFIG = {
	EVENT_TYPE: 'event_type',
	EVENT_TARGET: 'event_target',
	CAPTURE: 'capture',
	HTML_ELEM_WITH_PATH: 'el_with_path',
	HTML_ELEM_WITH_TEXT_CONTENT: 'el_with_txt',
	HTML_ELEM_WITH_CHILDREN: 'el_with_children',
	WITH_CURRENT_TARGET: 'el_with_cur_target',
	WITH_RELATED_TARGET: 'el_with_rel_target'
};

/** @const sensor type name */
uxperf.profiler.SensorBrowserEvent.TYPE_NAME = 'browser_event';

/**
 * Default configuration
 */
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG = {};
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG[uxperf.profiler.SensorBase.CONFIG.TYPE_KEY] = uxperf.profiler.SensorBrowserEvent.TYPE_NAME;
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG[uxperf.profiler.SensorBase.CONFIG.AUTOINIT_KEY] = uxperf.FactoryBase.AUTOINIT_MODE.ON_DOM_READY;
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG[uxperf.profiler.SensorBrowserEvent.CONFIG.EVENT_TARGET] = document;
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG[uxperf.profiler.SensorBrowserEvent.CONFIG.EVENT_TYPE] = goog.events.EventType.CLICK;
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG[uxperf.profiler.SensorBrowserEvent.CONFIG.CAPTURE] = true;
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG[uxperf.profiler.SensorBrowserEvent.CONFIG.HTML_ELEM_WITH_PATH] = true;
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG[uxperf.profiler.SensorBrowserEvent.CONFIG.HTML_ELEM_WITH_TEXT_CONTENT] = false;
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG[uxperf.profiler.SensorBrowserEvent.CONFIG.HTML_ELEM_WITH_CHILDREN] = false;
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG[uxperf.profiler.SensorBrowserEvent.CONFIG.WITH_CURRENT_TARGET] = true;
uxperf.profiler.SensorBrowserEvent.DEFAULT_CONFIG[uxperf.profiler.SensorBrowserEvent.CONFIG.WITH_RELATED_TARGET] = false;

/**
 * Buffer Item related to browser events
 *
 * @param {!goog.events.BrowserEvent} event standardized event object for browser event
 * @param {!uxperf.profiler.SensorBrowserEvent} sensor source of data
 * 
 * @constructor
 * @extends {uxperf.BufferItemSensor}
 */
uxperf.BufferItemBrowserEvent = function(event, sensor) {
	goog.base(this, sensor);
	
  /**
   * standardized event object for browser event
   * @type {goog.events.BrowserEvent}
   */
	this.event_ = event;
	
	//NOTE: workaround to bug in Browser Event implementation in Closure Library - there is timestamp field described in comment, but not implemented
	if (!this.event_.timestamp)
		this.event_.timestamp = this.event_.getBrowserEvent().timeStamp || goog.now();
	
	
	/**
	 * HTML Element path serialization flag
	 * @type {boolean}
	 */
	this.withPath_ = /** @type {boolean} */ sensor.getParam(uxperf.profiler.SensorBrowserEvent.CONFIG.HTML_ELEM_WITH_PATH) || false;

	/**
	 * HTML Element text content serialization flag
	 * @type {boolean}
	 */
	this.withTextContent_ = /** @type {boolean} */ sensor.getParam(uxperf.profiler.SensorBrowserEvent.CONFIG.HTML_ELEM_WITH_TEXT_CONTENT) || false;

	/**
	 * HTML Element children serialization flag
	 * @type {boolean}
	 */
	this.withChildren_ = /** @type {boolean} */ sensor.getParam(uxperf.profiler.SensorBrowserEvent.CONFIG.HTML_ELEM_WITH_CHILDREN) || false;
	
	/**
	 * current target serialization flag
	 * @type {boolean}
	 */
	this.withCurrentTarget_ = /** @type {boolean} */ sensor.getParam(uxperf.profiler.SensorBrowserEvent.CONFIG.WITH_CURRENT_TARGET) || false;
	
	/**
	 * related target serialization flag
	 * @type {boolean}
	 */
	this.withRelatedTarget_ = /** @type {boolean} */ sensor.getParam(uxperf.profiler.SensorBrowserEvent.CONFIG.WITH_RELATED_TARGET) || false;
};
goog.inherits(uxperf.BufferItemBrowserEvent, uxperf.BufferItemSensor);

/**
 * serialization of HTML Element to json object
 * 
 * @param {Element|Node|string|null|undefined} node element to serialize
 * @return {Object} result json structure
 */
uxperf.BufferItemBrowserEvent.prototype.serializeHtmlElement = function(node) {
  if (goog.DEBUG) LOG.info('BufferItemBrowserEvent - serialize HTML element : ' + node);
	if (!node) return null;
  var el = /** @type {Element} */ goog.dom.getElement(/** @type {Element|string} */ (node));
	
  /**
   * @type {Object}
   */
  var res = { 'tagname' : el.nodeName };
	
  if (el.id) res['id'] = el.id;
	
  if (el.name) res['name'] = el.name;

  if (this.withTextContent_) res['text'] = el.textContent;
  
  if (this.withChildren_) {
    var c = goog.dom.getChildren(el);
    if (c && c.length>0) {
      res['children'] = [];
      for (var i=0; i<c.length; i++)
        res['children'].push(this.serializeHtmlElement(c[i]));
    }
  }
  
	//NOTE: this part must be last because modification of el value
  if (this.withPath_) {
    var path = [];
    do {
      path.unshift(el.nodeName + (el.className ? ' class="' + el.className + '"' : ''));
    } while ((el.nodeName.toLowerCase() != 'html') && (el = el.parentNode));
    res['path'] = path;
  }
  
  return res;
};

/** @inheritDoc */
uxperf.BufferItemBrowserEvent.prototype.serialize = function() {
  if (goog.DEBUG) LOG.info('BufferItemBrowserEvent - serialize');
  /** @type {Object} */
  var res = uxperf.BufferItemBrowserEvent.superClass_.serialize.call(this);

  res['event_type'] = this.event_.type;
  res['timestamp'] = this.event_.timestamp;
  res['offsetX'] = this.event_.offsetX;
  res['offsetY'] = this.event_.offsetY;
  res['clientX'] = this.event_.clientX;
  res['clientY'] = this.event_.clientY;
  res['screenX'] = this.event_.screenX;
  res['screenY'] = this.event_.screenY;
  res['button'] = this.event_.button;
  res['keyCode'] = this.event_.keyCode;
  res['ctrlKey'] = this.event_.ctrlKey;
  res['altKey'] = this.event_.altKey;
  res['shiftKey'] = this.event_.shiftKey;
  res['metaKey'] = this.event_.metaKey;
  res['target'] = this.serializeHtmlElement(this.event_.target);
	if (this.withCurrentTarget_) res['currentTarget'] = this.serializeHtmlElement(this.event_.currentTarget);
  if (this.withRelatedTarget_) res['relatedTarget'] = this.serializeHtmlElement(this.event_.relatedTarget);
	
	//NOTE - to do if there will be need for history tracing
  //res['state'] = state; //History state object - simple serialization should be replaced with some simplified representation
	
	return res;
};
